package com.gjy.util.encryption.sm;

import com.google.common.collect.Maps;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 注意事项：前端 sm-crypto 使用sm2签名时用的是 Hex 这里用的是 Base64 需要转换下
 * @author gjy
 * @version 1.0
 * @since 2025-07-07 09:03:50
 */
public class SmUtil {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static String sm3Hash(String input) {
        SM3Digest digest = new SM3Digest();
        byte[] inputBytes = input.getBytes();
        digest.update(inputBytes, 0, inputBytes.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return Hex.toHexString(hash);
    }

    public static String sm4GenerateKey() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance("SM4", "BC");
        kg.init(128, new SecureRandom());
        return Base64.getEncoder().encodeToString(kg.generateKey().getEncoded());
    }

    // CBC 模式加密
    public static String sm4EncryptCbc(String plainText, String base64Key, String iv) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());

        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(plainText.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    // CBC 模式解密  iv16字节
    public static String sm4DecryptCbc(String base64CipherText, String base64Key, String iv) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());

        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(base64CipherText));
        return new String(decrypted);
    }

    private static final byte[] KEY = Hex.decode("0123456789abcdeffedcba9876543210");
    private static final byte[] IV = Hex.decode("00000000000000000000000000000000");

    public static String sm4Encrypt(String plainText) {
        try {
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine()));
            cipher.init(true, new ParametersWithIV(new KeyParameter(KEY), IV));
            byte[] input = plainText.getBytes(StandardCharsets.UTF_8);
            byte[] output = new byte[cipher.getOutputSize(input.length)];
            int len = cipher.processBytes(input, 0, input.length, output, 0);
            len += cipher.doFinal(output, len);
            return Hex.toHexString(output, 0, len);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String sm4Decrypt(String cipherTextHex) {
        try {
            byte[] cipherText = Hex.decode(cipherTextHex);
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine()));
            cipher.init(false, new ParametersWithIV(new KeyParameter(KEY), IV));
            byte[] output = new byte[cipher.getOutputSize(cipherText.length)];
            int len = cipher.processBytes(cipherText, 0, cipherText.length, output, 0);
            len += cipher.doFinal(output, len);
            return new String(output, 0, len, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Map<String, String> sm2GenerateKeyPair() throws Exception {
        // 初始化密钥生成器
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
        kpg.initialize(new ECGenParameterSpec("sm2p256v1"));
        KeyPair keyPair = kpg.generateKeyPair();

        // 转换为Base64字符串
        String pubKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String priKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());

        HashMap<String, String> map = Maps.newHashMap();
        map.put("pubK", pubKey);
        map.put("priK", priKey);
        return map;
    }

    public static Key sm2Base64ToKey(String base64PubKey, Boolean flag) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64PubKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        if (flag) {
            // Base64公钥字符串 → PublicKey对象
            return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
        } else {
            // Base64私钥字符串 → PrivateKey对象
            return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
        }

    }

    // 私钥签名
    public static String sm2Sign(String base64PriKey, String data) throws Exception {
        Key privateKey = sm2Base64ToKey(base64PriKey, false);
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initSign((PrivateKey) privateKey);
        signature.update(data.getBytes());
        byte[] signBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signBytes); // 返回Base64签名值
    }

    // 公钥验签
    public static boolean sm2Verify(String base64PubKey, String data, String base64Sign) throws Exception {
        Key publicKey = sm2Base64ToKey(base64PubKey, true);
        Signature signature = Signature.getInstance("SM3withSM2", "BC");
        signature.initVerify((PublicKey) publicKey);
        signature.update(data.getBytes());
        return signature.verify(Base64.getDecoder().decode(base64Sign));
    }

    // 私钥签名
    public static String sm2SignWithSinger(String base64PriKey, String data, String id) throws Exception {
        Key privateKey = sm2Base64ToKey(base64PriKey, false);
        if (privateKey instanceof BCECPrivateKey) {
            BCECPrivateKey priK = (BCECPrivateKey) privateKey;
            // 提取椭圆曲线域参数
            ECParameterSpec ecSpec = priK.getParameters();
            ECDomainParameters domainParams = new ECDomainParameters(ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
            // 构建BC专属公钥参数
            ECPrivateKeyParameters parameters = new ECPrivateKeyParameters(priK.getD(), domainParams);

            SM2Signer sm2Signer = new SM2Signer();
            sm2Signer.init(true, new ParametersWithID(parameters, id.getBytes(StandardCharsets.UTF_8)));
            sm2Signer.update(data.getBytes(), 0, data.getBytes().length);
            byte[] signBytes = sm2Signer.generateSignature();
            return Base64.getEncoder().encodeToString(signBytes); // 返回Base64签名值
        } else {
            return "";
        }
    }

    // 公钥验签
    public static boolean sm2VerifyWithSinger(String base64PubKey, String data, String base64Sign, String id) throws Exception {
        Key publicKey = sm2Base64ToKey(base64PubKey, true);
        if (publicKey instanceof BCECPublicKey) {
            BCECPublicKey bcPubKey = (BCECPublicKey) publicKey;
            // 提取椭圆曲线域参数
            ECParameterSpec ecSpec = bcPubKey.getParameters();
            ECDomainParameters domainParams = new ECDomainParameters(ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
            // 构建BC专属公钥参数
            ECPublicKeyParameters parameters = new ECPublicKeyParameters(bcPubKey.getQ(), domainParams);
            SM2Signer sm2Signer = new SM2Signer();
            sm2Signer.init(false, new ParametersWithID(parameters, id.getBytes(StandardCharsets.UTF_8)));
            sm2Signer.update(data.getBytes(), 0, data.getBytes().length);
            return sm2Signer.verifySignature(Base64.getDecoder().decode(base64Sign));
        } else {
            return false;
        }
    }

    // 公钥加密
    public static String sm2Encrypt(String base64PubKey, String plaintext) throws Exception {
        Key publicKey = sm2Base64ToKey(base64PubKey, true);
        ECPublicKeyParameters ecPub = (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter((PublicKey) publicKey);
        SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        engine.init(true, new ParametersWithRandom(ecPub, new SecureRandom()));
        byte[] encrypted = engine.processBlock(plaintext.getBytes(), 0, plaintext.length());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    // 私钥解密
    public static String sm2Decrypt(String base64PriKey, String base64Ciphertext) throws Exception {
        Key privateKey = sm2Base64ToKey(base64PriKey, false);
        ECPrivateKeyParameters ecPri = (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter((PrivateKey) privateKey);
        SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        engine.init(false, ecPri);
        byte[] decode = Base64.getDecoder().decode(base64Ciphertext);
        byte[] decrypted = engine.processBlock(decode, 0, decode.length);
        return new String(decrypted);
    }

    public static AsymmetricCipherKeyPair generateKeyPair() {
        X9ECParameters sm2Curve = ECNamedCurveTable.getByName("sm2p256v1");
        ECDomainParameters domainParams = new ECDomainParameters(sm2Curve.getCurve(), sm2Curve.getG(), sm2Curve.getN(), sm2Curve.getH());

        // 2. 生成静态密钥对
        ECKeyPairGenerator staticGen = new ECKeyPairGenerator();
        staticGen.init(new ECKeyGenerationParameters(domainParams, new SecureRandom()));
        return staticGen.generateKeyPair();
    }

    public static byte[] sm2Encrypt(ECPublicKeyParameters publicKey, byte[] data) {
        SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        ParametersWithRandom params = new ParametersWithRandom(publicKey, new SecureRandom());
        engine.init(true, params); // true 表示加密模式

        try {
            return engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException("SM2加密失败", e);
        }
    }

    public static byte[] sm2Decrypt(ECPrivateKeyParameters privateKey, byte[] encrypted) {
        SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        engine.init(false, privateKey); // false 表示解密模式
        try {
            return engine.processBlock(encrypted, 0, encrypted.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException("SM2解密失败", e);
        }
    }

}
